Traditionally, software compilers produce native code, that is, binary code that is specific to the machine on which the code will run. The native (unmanaged) code produced by traditional compilers typically includes everything an operating system needs to run the code, but little else. In contrast, in virtual machine environments, source code is compiled to an intermediate byte code representation that is not specific to any particular machine. In addition, the output of a managed code compiler typically includes considerably more information than just binary code. The additional information describes characteristics of the binary code and is sometimes called metadata: a generic term for data that describes other data, where in this case, the described data is the binary code. The container that contains the intermediate byte code and metadata is sometimes called a managed assembly. These containers can also be referred to as class files, Java archives or Java modules. The term “assembly” or “managed assembly” as used herein refers to any such container of byte code and metadata.
In an unmanaged environment, at runtime, pre-existing native instructions are loaded into memory and are executed. In a managed environment, at runtime, the managed assembly can be compiled or translated into native binary instructions right before execution. That is, the managed assembly can be loaded into memory and compiled by a just-in-time (JIT) or on-the-fly compiler into machine-specific and runtime-specific instructions, which are then executed. While the compilation/translation phase of the managed environment involves additional processing that may affect performance, on the other hand, the JIT/on-the-fly compiler may be able to make environment-specific optimizations that an unmanaged environment is unable to take advantage of. For example, if a JIT/on-the-fly compiler knows that the code that is executing is running with full trust, it can skip certain expensive security checks that could not be skipped in an unmanaged environment.
In a managed environment, instead of compiling a managed assembly right before it is executed, the source code or intermediate byte code can be compiled to native binaries by an optimizing compiler and stored (cached) before being run on a host system. For example, a native image generator such as NGEN may produce a native binary image for the environment by converting the intermediate byte code into native machine instructions in binary before a request is received to execute the assembly. In this way, at runtime, pre-existing executable code can be loaded and run without the second compilation/translation phase, in a fashion similar to that of a traditional native code environment. Although caching is intended to make the assembly execute faster by removing the JIT/on-the-fly compilation/translation process at runtime, performance may not be improved because environment-specific optimizations made by a JIT/on-the-fly compiler, such as the optimization described above, cannot be done when caching assemblies beforehand.
As stated above, most native languages do not keep track of information about the internal structure of the source code. In contrast, because of the metadata included with the intermediate byte code produced in managed environments, code that takes advantage of currently existing objects, creates new objects, inherits from existing objects and so on, can be readily generated at runtime. Runtime-generated code may require a special mechanism to be used to create or load the dynamic code without loading the runtime-generated code from a long-term storage medium (e.g., hard disk). Such a mechanism shall be referred to as Reflection Emit in the rest of this document. Code that is generated at runtime, which is also called dynamically-generated code, is typically compiled during every process instance to intermediate byte code, and then processed by the JIT/on-the-fly compiler. This is an every-process cost that may not be trivial.